﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

/* •—————————————————————————————————————————————————————————•
   | Adapted from MatthewPodwysocki's Functional C# Samples: |
   |                                                         |
   | http://code.msdn.microsoft.com/FunctionalCSharp         |
   •—————————————————————————————————————————————————————————• */

namespace SRTSolutions.Elevate
{
    using global::System;
    using global::System.Collections.Generic;

    /// <summary>
    /// Describes a type that may or may not contain a value.
    /// </summary>
    /// <typeparam name="T">The type of the optional value.</typeparam>
    public sealed class Option<T> : IEquatable<Option<T>>
    {
        private readonly T value;

        private static readonly Option<T> none = new Option<T>();

        private Option() { }

        private Option(T value)
        {
            this.value = value;
        }

        /// <summary>
        /// Gets a value indicating whether this instance is set to None (meaning it has no value).
        /// </summary>
        /// <value><c>true</c> if this instance is none; otherwise, <c>false</c>.</value>
        public bool IsNone
        {
            get { return this == none; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance contains a value.
        /// </summary>
        /// <value><c>true</c> if this instance is some; otherwise, <c>false</c>.</value>
        public bool IsSome
        {
            get { return !IsNone; }
        }

        /// <summary>
        /// Gets the value for this instance.
        /// </summary>
        /// <value>The value.</value>
        public T Value
        {
            get
            {
                if (IsSome)
                    return value;

                throw new InvalidOperationException();
            }
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (obj is Option<T>)
                return Equals((Option<T>)obj);

            return false;
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public bool Equals(Option<T> other)
        {
            if (IsNone)
                return other.IsNone;

            return EqualityComparer<T>.Default.Equals(value, other.value);
        }

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            if (IsNone)
                return 0;

            return EqualityComparer<T>.Default.GetHashCode(value);
        }

        /// <summary>
        /// Returns a "None" instance for this type.
        /// </summary>
        /// <value>The none.</value>
        public static Option<T> None
        {
            get { return none; }
        }

        /// <summary>
        /// Returns a new Option type containing a value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static Option<T> Some(T value)
        {
            return new Option<T>(value);
        }
    }

    /// <summary>
    /// Describes a type that may or may not contain a value.
    /// </summary>
    public static class Option
    {
        /// <summary>
        /// Returns a new Option type containing a value.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static Option<T> Some<T>(T value)
        {
            return Option<T>.Some(value);
        }
    }

}